<?php
/**
 * Created by PhpStorm.
 * User: dy
 * Date: 2020/1/10
 * Time: 14:21
 */
namespace util;
use Exception;

class FileUtil
{
    protected static $instance = null;
    private function __construct()
    {
    }
    private function __clone()
    {
    }

    public static function instance(){
        if(empty(self::$instance)){
            self::$instance = new static();
        }
        return self::$instance;
    }
    /**
     * 输出、文件下载，断点续传支持
     * 默认以附件方式下载；$download为false时则为输出文件
     * 视频播放拖拽：流媒体服务器
     * 文件缓存：http://blog.csdn.net/eroswang/article/details/8302191
     */
    function file_put_out($file,$download=-1,$downFilename=false,$httpSendFile = false){
        $error = false;
        if (!file_exists($file)){
            $error = 'file not exists';
        }else if (!$this->path_readable($file)){
            $error = 'file not readable';
        }else if (!$fp = @fopen($file, "rb")){
            $error = 'file open error!';
        }
        if($error !== false){
            if($downFilename === false){
                throw new \RuntimeException($error,400);
            }else{
                throw new \RuntimeException($error,400);
            }
        }

        $start= 0;
        $file_size = $this->get_filesize($file);
        $end  = $file_size - 1;
        @ob_end_clean();
        @set_time_limit(0);

        $time = gmdate('D, d M Y H:i:s',filemtime($file));
        $filename = $this->get_path_this($file);
        if($downFilename !== false){
            $filename = $downFilename;
        }

        $mime = $this->get_file_mime($this->get_path_ext($filename));
        if ($download === -1 && !$this->mime_support($mime)){
            $download = true;
        }
        $headerName = rawurlencode($this->iconv_app($filename));
        $headerName = '"'.$headerName."\"; filename*=utf-8''".$headerName;
        if ($download) {
            header('Content-Type: application/octet-stream');
            header('Content-Transfer-Encoding: binary');
            header('Content-Disposition: attachment;filename='.$headerName);
        }else{
            header('Content-Type: '.$mime);
            header('Content-Disposition: inline;filename='.$headerName);
            if(strstr($mime,'text/')){
                //$charset = get_charset(file_get_contents($file));
                header('Content-Type: '.$mime.'; charset=');//避免自动追加utf8导致gbk网页乱码
            }
        }

        //缓存文件
        header('Expires: '.gmdate('D, d M Y H:i:s',time()+3600*24*20).' GMT');
        header('Cache-Pragma: public');
        header('Pragma: public');
        header('Cache-Control: cache, must-revalidate');
        if (isset($_SERVER['If-Modified-Since']) &&
            (strtotime($_SERVER['If-Modified-Since']) == filemtime($file))) {
            header('304 Not Modified', true, 304);
            exit;
        }
        $etag = '"'.md5($time.$file_size).'"';
        if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag){
            header("Etag: ".$etag, true, 304);
            exit;
        }
        header('Etag: '.$etag);
        header('Last-Modified: '.$time.' GMT');
        header("X-OutFileName: ".$filename);
        header("X-Powered-By: ddAdmin.");
        header("X-FileSize: ".$file_size);

        //调用webserver下载
        $server = strtolower($_SERVER['SERVER_SOFTWARE']);
        if($server && $httpSendFile){
            if(strstr($server,'nginx')){//nginx
                header("X-Sendfile: ".$file);
            }else if(strstr($server,'apache')){ //apache
                header('X-Accel-Redirect: '.$file);
            }else if(strstr($server,'http')){//light http
                header( "X-LIGHTTPD-send-file: " . $file);
            }else{
                print 'SERVER_SOFTWARE Not Download File';
            }
            return;
        }

        //远程路径不支持断点续传；打开zip内部文件
        if(!file_exists($file)){
            header('HTTP/1.1 200 OK');
            header('Content-Length: '.($end+1));
            return;
        }
        header("Accept-Ranges: bytes");
        if (isset($_SERVER['HTTP_RANGE'])){
            if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)){
                $start	= intval($matches[1]);
                if (!empty($matches[2])){
                    $end = intval($matches[2]);
                }
            }
            header('HTTP/1.1 206 Partial Content');
        }else{
            header('HTTP/1.1 200 OK');
        }
        if(isset($_GET['start'])){//flash video
            $start = intval($_GET['start']);
        }
        header('Content-Length:' . (($end - $start) + 1));
        if (isset($_SERVER['HTTP_RANGE']) || isset($_GET['start'])){
            header("Content-Range: bytes $start-$end/".$file_size);
        }

        //输出文件
        $cur = $start;
        fseek($fp, $start,0);
        while(!feof($fp) && $cur <= $end){ // && (connection_status() == 0)
            print fread($fp, min(1024 * 200, ($end - $cur) + 1));
            $cur += 1024 *200;
            flush();
        }
        fclose($fp);
    }

    function path_readable($path){
        $result = intval(is_readable($path));
        if($result){
            return $result;
        }
        $mode = $this->get_mode($path);
        if( $mode &&
            strlen($mode) == 18 &&
            substr($mode,-9,1) == 'r'){// -rwx rwx rwx(0777)
            return true;
        }
        return false;
    }

    /**
     * 获取文件(夹)权限 rwx_rwx_rwx
     */
    function get_mode($file){
        $Mode = @fileperms($file);
        $theMode = ' '.decoct($Mode);
        $theMode = substr($theMode,-4);
        $Owner = array();$Group=array();$World=array();
        if ($Mode &0x1000) $Type = 'p'; // FIFO pipe
        elseif ($Mode &0x2000) $Type = 'c'; // Character special
        elseif ($Mode &0x4000) $Type = 'd'; // Directory
        elseif ($Mode &0x6000) $Type = 'b'; // Block special
        elseif ($Mode &0x8000) $Type = '-'; // Regular
        elseif ($Mode &0xA000) $Type = 'l'; // Symbolic Link
        elseif ($Mode &0xC000) $Type = 's'; // Socket
        else $Type = 'u'; // UNKNOWN
        // Determine les permissions par Groupe
        $Owner['r'] = ($Mode &00400) ? 'r' : '-';
        $Owner['w'] = ($Mode &00200) ? 'w' : '-';
        $Owner['x'] = ($Mode &00100) ? 'x' : '-';
        $Group['r'] = ($Mode &00040) ? 'r' : '-';
        $Group['w'] = ($Mode &00020) ? 'w' : '-';
        $Group['e'] = ($Mode &00010) ? 'x' : '-';
        $World['r'] = ($Mode &00004) ? 'r' : '-';
        $World['w'] = ($Mode &00002) ? 'w' : '-';
        $World['e'] = ($Mode &00001) ? 'x' : '-';
        // Adjuste pour SUID, SGID et sticky bit
        if ($Mode &0x800) $Owner['e'] = ($Owner['e'] == 'x') ? 's' : 'S';
        if ($Mode &0x400) $Group['e'] = ($Group['e'] == 'x') ? 's' : 'S';
        if ($Mode &0x200) $World['e'] = ($World['e'] == 'x') ? 't' : 'T';
        $Mode = $Type.$Owner['r'].$Owner['w'].$Owner['x'].' '.
            $Group['r'].$Group['w'].$Group['e'].' '.
            $World['r'].$World['w'].$World['e'];
        return $Mode.'('.$theMode.')';
    }

    //filesize 解决大于2G 大小问题
//http://stackoverflow.com/questions/5501451/php-x86-how-to-get-filesize-of-2-gb-file-without-external-program
    function get_filesize($path){
        $result = false;
        $fp = fopen($path,"r");
        if(! $fp = fopen($path,"r")) return $result;
        if(PHP_INT_SIZE >= 8 ){ //64bit
            $result = (float)(abs(sprintf("%u",@filesize($path))));
        }else{
            if (fseek($fp, 0, SEEK_END) === 0) {
                $result = 0.0;
                $step = 0x7FFFFFFF;
                while ($step > 0) {
                    if (fseek($fp, - $step, SEEK_CUR) === 0) {
                        $result += floatval($step);
                    } else {
                        $step >>= 1;
                    }
                }
            }else{
                static $iswin;
                if (!isset($iswin)) {
                    $iswin = (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN');
                }
                static $exec_works;
                if (!isset($exec_works)) {
                    $exec_works = (function_exists('exec') && !ini_get('safe_mode') && @exec('echo EXEC') == 'EXEC');
                }
                if ($iswin && class_exists("COM")) {
                    try {
                        $fsobj = new COM('Scripting.FileSystemObject');
                        $f = $fsobj->GetFile( realpath($path) );
                        $size = $f->Size;
                    } catch (Exception $e) {
                        $size = null;
                    }
                    if (is_numeric($size)) {
                        $result = $size;
                    }
                }else if ($exec_works){
                    $cmd = ($iswin) ? "for %F in (\"$path\") do @echo %~zF" : "stat -c%s \"$path\"";
                    @exec($cmd, $output);
                    if (is_array($output) && is_numeric($size = trim(implode("\n", $output)))) {
                        $result = $size;
                    }
                }else{
                    $result = filesize($path);
                }
            }
        }
        fclose($fp);
        return $result;
    }

    /**
     * 获取一个路径(文件夹&文件) 当前文件[夹]名
     * test/11/ ==>11 test/1.c  ==>1.c
     */
    function get_path_this($path){
        $path = str_replace('\\','/', rtrim($path,'/'));
        $pos = strrpos($path,'/');
        if($pos === false){
            return $path;
        }
        return substr($path,$pos+1);
    }

    /**
     * 获取扩展名
     */
    function get_path_ext($path){
        $name = $this->get_path_this($path);
        $ext = '';
        if(strstr($name,'.')){
            $ext = substr($name,strrpos($name,'.')+1);
            $ext = strtolower($ext);
        }
        if (strlen($ext)>3 && preg_match("/([\x81-\xfe][\x40-\xfe])/", $ext, $match)) {
            $ext = '';
        }
        return htmlspecialchars($ext);
    }

    // 传入参数为程序编码时，有传出，则用程序编码，
    // 传入参数没有和输出无关时，则传入时处理成系统编码。
    function iconv_app($str){
        global $config;
        $result = $this->iconv_to($str,$config['systemCharset'], $config['appCharset']);
        return $result;
    }
    function iconv_to($str,$from,$to){
        if (strtolower($from) == strtolower($to)){
            return $str;
        }
        if (!function_exists('iconv')){
            return $str;
        }
        //尝试用mb转换；android环境部分问题解决
        if(function_exists('mb_convert_encoding')){
            $result = @mb_convert_encoding($str,$to,$from);
        }else{
            $result = @iconv($from, $to, $str);
        }
        if(strlen($result)==0){
            return $str;
        }
        return $result;
    }

    //根据扩展名获取mime
    function get_file_mime($ext){
        $mimetypes = array(
            "323" => "text/h323",
            "acx" => "application/internet-property-stream",
            "ai" => "application/postscript",
            "aif" => "audio/x-aiff",
            "aifc" => "audio/x-aiff",
            "aiff" => "audio/x-aiff",
            "asf" => "video/x-ms-asf",
            "asr" => "video/x-ms-asf",
            "asx" => "video/x-ms-asf",
            "au" => "audio/basic",
            "avi" => "video/x-msvideo",
            "axs" => "application/olescript",
            "bas" => "text/plain",
            "bcpio" => "application/x-bcpio",
            "bin" => "application/octet-stream",
            "bmp" => "image/bmp",
            "c" => "text/plain",
            "cat" => "application/vnd.ms-pkiseccat",
            "cdf" => "application/x-cdf",
            "cer" => "application/x-x509-ca-cert",
            "class" => "application/octet-stream",
            "clp" => "application/x-msclip",
            "cmx" => "image/x-cmx",
            "cod" => "image/cis-cod",
            "cpio" => "application/x-cpio",
            "crd" => "application/x-mscardfile",
            "crl" => "application/pkix-crl",
            "crt" => "application/x-x509-ca-cert",
            "csh" => "application/x-csh",
            "css" => "text/css",
            "dcr" => "application/x-director",
            "der" => "application/x-x509-ca-cert",
            "dir" => "application/x-director",
            "dll" => "application/x-msdownload",
            "dms" => "application/octet-stream",
            "doc" => "application/msword",
            "docx" => "application/msword",
            "dot" => "application/msword",
            "dvi" => "application/x-dvi",
            "dxr" => "application/x-director",
            "eps" => "application/postscript",
            "etx" => "text/x-setext",
            "evy" => "application/envoy",
            "exe" => "application/octet-stream",
            "fif" => "application/fractals",
            "flr" => "x-world/x-vrml",
            "flv" => "video/x-flv",
            "f4v" => "application/octet-stream",
            "gif" => "image/gif",
            "gtar" => "application/x-gtar",
            "gz" => "application/x-gzip",
            "h" => "text/plain",
            "hdf" => "application/x-hdf",
            "hlp" => "application/winhlp",
            "hqx" => "application/mac-binhex40",
            "hta" => "application/hta",
            "htc" => "text/x-component",
            "htm" => "text/html",
            "html" => "text/html",
            "htt" => "text/webviewhtml",
            "ico" => "image/x-icon",
            "ief" => "image/ief",
            "iii" => "application/x-iphone",
            "ins" => "application/x-internet-signup",
            "isp" => "application/x-internet-signup",
            "jfif" => "image/pipeg",
            "jpe" => "image/jpeg",
            "jpeg" => "image/jpeg",
            "jpg" => "image/jpeg",
            "js" => "application/javascript",
            "json" => "application/json",
            "latex" => "application/x-latex",
            "lha" => "application/octet-stream",
            "lsf" => "video/x-la-asf",
            "lsx" => "video/x-la-asf",
            "lzh" => "application/octet-stream",
            "m13" => "application/x-msmediaview",
            "m14" => "application/x-msmediaview",
            "m3u" => "audio/x-mpegurl",
            'm4a' => "audio/mp4",
            'm4v' => "audio/mp4",
            "man" => "application/x-troff-man",
            "mdb" => "application/x-msaccess",
            "me" => "application/x-troff-me",
            "mht" => "message/rfc822",
            "mhtml" => "message/rfc822",
            "mid" => "audio/mid",
            "mny" => "application/x-msmoney",
            "mov" => "video/quicktime",
            "movie" => "video/x-sgi-movie",
            "mp2" => "video/mpeg",
            "mp3" => "audio/mpeg",
            "mp4" => "video/mp4",
            "mp4v" => "video/mp4",
            "mpa" => "video/mpeg",
            "mpe" => "video/mpeg",
            "mpeg" => "video/mpeg",
            "mpg" => "video/mpeg",
            "mpp" => "application/vnd.ms-project",
            "mpv2" => "video/mpeg",
            "ms" => "application/x-troff-ms",
            "mvb" => "application/x-msmediaview",
            "nws" => "message/rfc822",
            "oda" => "application/oda",
            "ogg" => "audio/ogg",
            "oga" => "audio/ogg",
            "ogv" => "audio/ogg",
            "p10" => "application/pkcs10",
            "p12" => "application/x-pkcs12",
            "p7b" => "application/x-pkcs7-certificates",
            "p7c" => "application/x-pkcs7-mime",
            "p7m" => "application/x-pkcs7-mime",
            "p7r" => "application/x-pkcs7-certreqresp",
            "p7s" => "application/x-pkcs7-signature",
            "pbm" => "image/x-portable-bitmap",
            "pdf" => "application/pdf",
            "pfx" => "application/x-pkcs12",
            "pgm" => "image/x-portable-graymap",
            "pko" => "application/ynd.ms-pkipko",
            "pma" => "application/x-perfmon",
            "pmc" => "application/x-perfmon",
            "pml" => "application/x-perfmon",
            "pmr" => "application/x-perfmon",
            "pmw" => "application/x-perfmon",
            "png" => "image/png",
            "pnm" => "image/x-portable-anymap",
            "pot," => "application/vnd.ms-powerpoint",
            "ppm" => "image/x-portable-pixmap",
            "pps" => "application/vnd.ms-powerpoint",
            "ppt" => "application/vnd.ms-powerpoint",
            "pptx" => "application/vnd.ms-powerpoint",
            "prf" => "application/pics-rules",
            "ps" => "application/postscript",
            "pub" => "application/x-mspublisher",
            "qt" => "video/quicktime",
            "ra" => "audio/x-pn-realaudio",
            "ram" => "audio/x-pn-realaudio",
            "ras" => "image/x-cmu-raster",
            "rgb" => "image/x-rgb",
            "rmi" => "audio/mid",
            "roff" => "application/x-troff",
            "rtf" => "application/rtf",
            "rtx" => "text/richtext",
            "scd" => "application/x-msschedule",
            "sct" => "text/scriptlet",
            "setpay" => "application/set-payment-initiation",
            "setreg" => "application/set-registration-initiation",
            "sh" => "application/x-sh",
            "shar" => "application/x-shar",
            "sit" => "application/x-stuffit",
            "snd" => "audio/basic",
            "spc" => "application/x-pkcs7-certificates",
            "spl" => "application/futuresplash",
            "src" => "application/x-wais-source",
            "sst" => "application/vnd.ms-pkicertstore",
            "stl" => "application/vnd.ms-pkistl",
            "stm" => "text/html",
            "svg" => "image/svg+xml",
            "sv4cpio" => "application/x-sv4cpio",
            "sv4crc" => "application/x-sv4crc",
            "swf" => "application/x-shockwave-flash",
            "t" => "application/x-troff",
            "tar" => "application/x-tar",
            "tcl" => "application/x-tcl",
            "tex" => "application/x-tex",
            "texi" => "application/x-texinfo",
            "texinfo" => "application/x-texinfo",
            "tgz" => "application/x-compressed",
            "tif" => "image/tiff",
            "tiff" => "image/tiff",
            "tr" => "application/x-troff",
            "trm" => "application/x-msterminal",
            "tsv" => "text/tab-separated-values",
            "txt" => "text/plain",
            "uls" => "text/iuls",
            "ustar" => "application/x-ustar",
            "vcf" => "text/x-vcard",
            "vrml" => "x-world/x-vrml",
            "wav" => "audio/wav",
            "wcm" => "application/vnd.ms-works",
            "wdb" => "application/vnd.ms-works",
            "webm" => "video/webm",
            "webmv" => "video/webm",
            "wks" => "application/vnd.ms-works",
            "wmf" => "application/x-msmetafile",
            "wps" => "application/vnd.ms-works",
            "wri" => "application/x-mswrite",
            "wrl" => "x-world/x-vrml",
            "wrz" => "x-world/x-vrml",
            "xaf" => "x-world/x-vrml",
            "xbm" => "image/x-xbitmap",
            "xla" => "application/vnd.ms-excel",
            "xlc" => "application/vnd.ms-excel",
            "xlm" => "application/vnd.ms-excel",
            "xls" => "application/vnd.ms-excel",
            "xlsx" => "application/vnd.ms-excel",
            "xlt" => "application/vnd.ms-excel",
            "xlw" => "application/vnd.ms-excel",
            "xof" => "x-world/x-vrml",
            "xpm" => "image/x-xpixmap",
            "xwd" => "image/x-xwindowdump",
            "z" => "application/x-compress",
            "zip" => "application/zip"
        );

        //代码 或文本浏览器输出
        $text = array('oexe','inc','inf','csv','log','asc','tsv');
        $code = array("abap","abc","as","ada","adb","htgroups","htpasswd","conf","htaccess","htgroups",
            "htpasswd","asciidoc","asm","ahk","bat","cmd","c9search_results","cpp","c","cc","cxx","h","hh","hpp",
            "cirru","cr","clj","cljs","CBL","COB","coffee","cf","cson","Cakefile","cfm","cs","css","curly","d",
            "di","dart","diff","patch","Dockerfile","dot","dummy","dummy","e","ejs","ex","exs","elm","erl",
            "hrl","frt","fs","ldr","ftl","gcode","feature",".gitignore","glsl","frag","vert","go","groovy",
            "haml","hbs","handlebars","tpl","mustache","hs","hx","html","htm","xhtml","erb","rhtml","ini",
            "cfg","prefs","io","jack","jade","java","js","jsm","json","jq","jsp","jsx","jl","tex","latex",
            "ltx","bib","lean","hlean","less","liquid","lisp","ls","logic","lql","lsl","lua","lp","lucene",
            "Makefile","GNUmakefile","makefile","OCamlMakefile","make","md","markdown","mask","matlab",
            "mel","mc","mush","mysql","nc","nix","m","mm","ml","mli","pas","p","pl","pm","pgsql","php","phtml",
            "ps1","praat","praatscript","psc","proc","plg","prolog","properties","proto","py","r","Rd",
            "Rhtml","rb","ru","gemspec","rake","Guardfile","Rakefile","Gemfile","rs","sass","scad","scala",
            "scm","rkt","scss","sh","bash",".bashrc","sjs","smarty","tpl","snippets","soy","space","sql",
            "styl","stylus","svg","tcl","tex","txt","textile","toml","twig","ts","typescript","str","vala",
            "vbs","vb","vm","v","vh","sv","svh","vhd","vhdl","xml","rdf","rss","log",
            "wsdl","xslt","atom","mathml","mml","xul","xbl","xaml","xq","yaml","yml","htm",
            "xib","storyboard","plist","csproj");
        if (array_key_exists($ext,$mimetypes)){
            return $mimetypes[$ext];
        }else{
            if(in_array($ext,$text) || in_array($ext,$code)){
                return "text/plain";
            }
            return 'application/octet-stream';
        }
    }

    // 浏览器是否直接打开
    function mime_support($mime){
        $arr_start = array(
            "text/",
            "image/",
            "audio/",
            "video/",
            "message/",
        );
        $arr_mime = array(
            "application/hta",
            "application/javascript",
            "application/json",
            "application/x-latex",
            "application/pdf",
            "application/x-shockwave-flash",
            "application/x-tex",
            "application/x-texinfo"
        );
        if(in_array($mime,$arr_mime)){
            return true;
        }
        foreach ($arr_start as $val) {
            if(substr($mime,0,strlen($val)) == $val){
                return true;
            }
        }
        return false;
    }
}